package ch.zhaw.skuh.turing.model.turingmaschine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class TuringChar extends Turing {

	// the char array
	// warning: blank has index 0
	private char[] charByIndex;
	private Map<Character, Integer> indexByChar;

	public abstract boolean validateInput(String input);

	public abstract void setTapes(String input);

	public abstract String getResult();

	public abstract String getResultPrediction();

	// save constructor: char-arrays
	protected TuringChar(int maxState, char[] alphabet, char blank,
			int[] acceptState, int tapeCount, String name) {
		super(maxState, alphabet.length, acceptState, tapeCount, name);
		setAlphabet(alphabet, blank);
	}

	// constructor with strings
	protected TuringChar(int maxState, String alphabet, char blank,
			int[] acceptState, int tapeCount, String name) {
		super(maxState, alphabet.length(), acceptState, tapeCount, name);
		setAlphabet(alphabet.toCharArray(), blank);
	}

	/*
	 * unused // construct digit-alphabet by highest digit, we are lazy today
	 * protected TuringChar(int maxState, int highestDigit, char blank, int[]
	 * acceptState, int tapeCount, String name) { super(maxState,
	 * highestDigit+1, acceptState, tapeCount, name);
	 * setAlphabet(TuringChar.toChar(highestDigit, blank), blank); }
	 * 
	 * //statics private static char[] toChar(int highestDigit, char blank){
	 * if(highestDigit>9){ throw new IndexOutOfBoundsException(); } char[]
	 * digits = new char[highestDigit+1]; digits[0] = blank; for(int
	 * i=1;i<highestDigit+1;i++){ digits[i] = Integer.toString(i).charAt(0); }
	 * return digits; }
	 */

	// init-functions
	private void setAlphabet(char[] alphabet, char blank) {
		charByIndex = alphabet;
		indexByChar = new HashMap<Character, Integer>(charByIndex.length);
		for (int i = 0; i < charByIndex.length; i++) {
			// check unique charByIndex
			indexByChar.put(charByIndex[i], i);
		}
	}

	// helper functions
	private int[] toPosition(char[] tapes) {
		int[] indexes = new int[tapes.length];
		for (int i = 0; i < indexes.length; i++) {
			indexes[i] = indexOf(tapes[i]);
		}
		return indexes;
	}

	private int indexOf(char c) {
		return indexByChar.get(c);
	}

	protected void addRule(int stateIn, char[] tapesIn, int stateOut,
			char[] tapesOut, int[] moves) {
/*
		// debug
		StringBuilder debug = new StringBuilder("Adding rule: q");
		debug.append(Integer.toString(stateIn));
		debug.append(", tapes: ");
		for (int i = 0; i < tapesIn.length; i++) {
			debug.append(new char[] { '[', tapesIn[i], ']' });
		}
		System.out.println(debug);
*/
		getMaschine().addRule(stateIn, toPosition(tapesIn), stateOut,
				toPosition(tapesOut), moves);
	}

	protected void addRule(int stateIn, String tapesIn, int stateOut,
			String tapesOut, int[] moves) {
		addRule(stateIn, tapesIn.toCharArray(), stateOut,
				tapesOut.toCharArray(), moves);
	}

	protected void addRule(int stateIn, String tapesIn, int stateOut,
			String tapesOut, String moves) {
		int[] intMoves = new int[getTapeCount()];
		// parse for "-" (minus)
		int iMoves = 0;
		for (int i = 0; i < moves.length(); i++) {
			if (moves.charAt(i) == '-') {
				intMoves[iMoves++] = (-1)
						* Integer
								.parseInt(Character.toString(moves.charAt(++i)));
			} else {
				intMoves[iMoves++] = Integer.parseInt(Character.toString(moves
						.charAt(i)));
			}
		}
		addRule(stateIn, tapesIn, stateOut, tapesOut, intMoves);
	}

	public void addRulePadded(int stateIn, String tapesIn, int stateOut,
			String tapesOut, String moves, int[] tapes) {
		moves = getPaddedMoves(moves, tapes, '0');
		tapesIn = getPaddedTapes(tapesIn, tapes);
		tapesOut = getPaddedTapes(tapesOut, tapes);
/*
		// debug
		StringBuilder debug = new StringBuilder("Adding padded rule: q");
		debug.append(Integer.toString(stateIn));
		debug.append(", tapes: ");
		for (int i = 0; i < tapesIn.length(); i++) {
			debug.append(new char[] { '[', tapesIn.charAt(i), ']' });
		}
		System.out.println(debug);
*/
		addRule(stateIn, tapesIn, stateOut, tapesOut, moves);
	}

	// getter/setter
	public final char[] getAlphabet() {
		return charByIndex;
	}

	public final char[][] getAlphabetCombinations() {
		char[][] combos = new char[(int) Math.pow(getAlphLength(),
				getTapeCount())][getTapeCount()];
		for (int i = 0; i < combos.length; i++) {
			combos[i] = calcTapeChars(i);
		}
		return combos;
	}

	public final List<char[]> getAlphabetCombinationsFiltered() {
		int max = (int) Math.pow(getAlphLength(), getTapeCount());
		ArrayList<char[]> combos = new ArrayList<char[]>(max);
		// check if any rule exists for this char combination
		for (int i = 0; i < max; i++) {
			for (int q = 0; q < getMaxState(); q++) {
				if (getMaschine().hasRule(q, i)) {
					char[] combo = calcTapeChars(i);
					combos.add(combo);
					break;
				}
			}
		}
		combos.trimToSize();
		return combos;
	}

	public boolean hasRule(int state, char[] read) {
		int[] readPos = toPosition(read);
		int[] rule = getMaschine().getRule(state, readPos);
		return (rule[0] != 0);
	}

	public int getRuleState(int state, char[] read) {
		int[] readPos = toPosition(read);
		int[] rule = getMaschine().getRule(state, readPos);
		return rule[1];
	}

	public int[] getRuleMoves(int state, char[] read) {
		int[] readPos = toPosition(read);
		int[] rule = getMaschine().getRule(state, readPos);
		int[] ret = new int[getTapeCount()];
		for (int i = 0; i < getTapeCount(); i++) {
			ret[i] = rule[2 + i];
		}
		return ret;
	}

	public char[] getRuleChars(int state, char[] read) {
		int[] readPos = toPosition(read);
		int[] rule = getMaschine().getRule(state, readPos);
		char[] ret = new char[getTapeCount()];
		char c;
		for (int i = 0; i < getTapeCount(); i++) {
			c = charByIndex[rule[2 + getTapeCount() + i]];
			ret[i] = c;
		}
		return ret;
	}

	private char[] calcTapeChars(int input) {
		char[] ret = new char[getTapeCount()];
		String s = Integer.toString(input, getAlphLength());
		// fill
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(s.length() - 1 - i);
			int cPos = Integer.parseInt(Character.toString(c), getAlphLength());
			ret[i] = charByIndex[cPos];
		}
		// pad with blanks
		for (int i = s.length(); i < ret.length; i++) {
			ret[i] = charByIndex[0];
		}

		return ret;
	}

	public char[][] getTapesPart(int offset) {
		int[][] intTapes = getMaschine().getTapesPart(offset);
		char[][] retTapes = new char[getTapeCount()][2 * offset + 1];
		for (int iTape = 0; iTape < getTapeCount(); iTape++) {
			for (int i = 0; i < 2 * offset + 1; i++) {
				int charPos = intTapes[iTape][i];
				retTapes[iTape][i] = charByIndex[charPos];
			}
		}
		return retTapes;
	}

	public char[] getTapesAtCurrent() {
		char[] retTapes = new char[getTapeCount()];
		int[] tapesCurrent = getMaschine().getTapesAtCurrent();
		for (int iTape = 0; iTape < getTapeCount(); iTape++) {
			retTapes[iTape] = charByIndex[tapesCurrent[iTape]];
		}
		return retTapes;
	}

	public String getTape(int index) {
		int[] tapeInt = getMaschine().getTape(index);
		StringBuilder sb = new StringBuilder(tapeInt.length);
		// find first and last position of non-blank
		int first = 0;
		int last = tapeInt.length - 1;
		// find first, forward
		for (; first < tapeInt.length; first++) {
			if (tapeInt[first] != 0) {
				break;
			}
		}
		// find last, backward
		for (; last > -1; last--) {
			if (tapeInt[last] != 0) {
				break;
			}
		}
		for (int i = first; i < last + 1; i++) {
			sb.append(charByIndex[tapeInt[i]]);
		}
		return sb.toString();
	}

	protected final void setTapes(char[][] input) {
		setTapes(input, 0);
	}

	protected final void setTapes(char[][] input, int startState) {
		// set positions in tapes to 0 (default)
		setTapes(input, new int[getTapeCount()], startState);
	}

	protected final void setTapes(char[][] input, int[] pos) {
		setTapes(input, pos, 0);
	}

	protected final void setTapes(char[][] input, int[] pos, int startState) {
		// convert char array to position array
		int[][] cPos = new int[getTapeCount()][];
		for (int i = 0; i < input.length; i++) {
			// could be null
			if (input[i] != null) {
				cPos[i] = toPosition(input[i]);
			}
		}
		// fill machine tape
		setTapes(cPos, pos, startState);
	}

	private String getPaddedMoves(String input, int[] tapes, char pad) {
		StringBuilder sb = new StringBuilder(getTapeCount());
		List<String> moves = new ArrayList<String>(getTapeCount());
		int inputIndex = 0;
		char c;
		for (int j = 0; j < tapes.length; j++) {
			// check "-"
			if (input.charAt(inputIndex) == '-') {
				inputIndex++;
				c = input.charAt(inputIndex++);
				moves.add("-" + Character.toString(c));
			} else {
				c = input.charAt(inputIndex++);
				moves.add(Character.toString(c));
			}
		}

		inputIndex = 0;
		for (int i = 0; i < getTapeCount(); i++) {
			// check if in tapes
			boolean found = false;
			for (int j = 0; j < tapes.length; j++) {
				if (i == tapes[j]) {
					sb.append(moves.get(j));
					found = true;
					break;
				}
			}
			// pad with blank
			if (found == false) {
				sb.append(pad);
			}
		}
		return sb.toString();
	}

	private String getPaddedTapes(String input, int[] tapes) {
		return getPaddedTapes(input, tapes, charByIndex[0]);
	}

	private String getPaddedTapes(String input, int[] tapes, char pad) {
		// checks
		if (input.length() > getTapeCount() || tapes.length > getTapeCount()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		if (input.length() != tapes.length) {
			throw new ArrayIndexOutOfBoundsException();
		}
		StringBuilder sb = new StringBuilder(getTapeCount());

		List<Character> moves = new ArrayList<Character>(getTapeCount());

		int inputIndex = 0;
		char c;
		for (int j = 0; j < tapes.length; j++) {
			c = input.charAt(inputIndex++);
			moves.add(c);
		}

		inputIndex = 0;
		for (int i = 0; i < getTapeCount(); i++) {
			// check if in tapes
			boolean found = false;
			for (int j = 0; j < tapes.length; j++) {
				if (i == tapes[j]) {
					sb.append(moves.get(j));
					found = true;
					break;
				}
			}
			// pad with blank
			if (found == false) {
				sb.append(pad);
			}
		}
		return sb.toString();
	}
}
